package com.link.base.core.basic.interceptors.mybatis.system;

import com.alibaba.fastjson.JSONObject;
import com.link.base.base.trackchild.model.Trackchild;
import com.link.base.base.trackdisplay.model.TrackDisplay;
import com.link.base.base.trackhead.model.Trackhead;
import com.link.base.user.model.CoreUser;
import com.link.core.basic.model.BasicModel;
import com.link.core.basic.service.KeyGenerateService;
import com.link.core.basic.service.snowflake.KeyGenerateUtil;
import com.link.core.cllog.LCLogger;
import com.link.core.util.DateUtil;
import com.link.core.util.UserUtil;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.commons.collections.map.CaseInsensitiveMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Invocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author: qinxuegang
 * @Description:
 * @Date: 2018/11/1 11:54
 */
@Service("updateInterceptor")
public class UpdateInterceptor implements SqlInterceptor {
    private static final Logger logger = LogManager.getLogger(UpdateInterceptor.class);
    @Resource
    private JdbcTemplate jdbcTemplate;
    @Resource
    private InterceptorUtils interceptorUtils;
    @Resource
    private KeyGenerateService keyGenerateService;

    /**
     * 识别sql中？
     */
    private static Pattern REPLACE_PATTERN = Pattern.compile("\\?");
    /**
     * SQL中的where
     */
    private static final String WHERE = "where";
    /**
     * SQL中的=
     */
    private static final String EQUALS = "=";

    /**
     * //获取对象的model，这里还是需要考虑不传入对象，而是传入自定义的内容，这个可能需要考虑获取
     * 这里需要限制（1.当传入的对象是String、map、list或是传入的对象与需要更新的对象不为同一个对象时，会造成不知道更新的对象是哪一个，以及当同时更新两张表时，
     * 传入的对象为一个的时候也会出现相同问题，因此这里对象这里建议配置表名）
     *
     * @param invocation
     * @param boundSql
     * @return
     */
    @Override
    public String doInterceptor(Invocation invocation, BoundSql boundSql) {
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        Object parameterObject = boundSql.getParameterObject();
        Statement stmt = null;
        try {
            String updateSql = boundSql.getSql().toLowerCase();
            String rgex = "/\\*.*?\\*/";
            updateSql = InterceptorUtils.getSubUtil(updateSql, rgex).toLowerCase();
            stmt = CCJSqlParserUtil.parse(updateSql);
            Update update = (Update) stmt;
            List<Table> tables = update.getTables();
            Map<String, String> map = new CaseInsensitiveMap(50);
            for (Table temp : tables) {
                if (temp.getAlias() == null) {
                    map.put(temp.getName(), "");
                } else {
                    map.put(temp.getName(), temp.getAlias().getName());
                }
            }
            //判断更新的表中是否有需要审计追踪的
            Map<String, Object> headAndChild = interceptorUtils.getHeadAndChild(map.keySet(), "trackUpd");
            // 记录更新字段的位置
            Map<String, String> index = new CaseInsensitiveMap(300);
            // 记录更新字段后的值
            Map<String, Object> beforAfter = new CaseInsensitiveMap(300);
            if (headAndChild.get("trackheads") == null) {
                return boundSql.getSql();
            }
            CoreUser user = UserUtil.getUser();
//            Long batchNum = keyGenerateService.keyGenerate();
            //修改生成id的方式是为了适配目前的多数据源切换，使用keyGenerateService会将数据源切回为主数据源
            Long batchNum = KeyGenerateUtil.keyGenerate();
            for (Trackhead trackhead : (List<Trackhead>) headAndChild.get("trackheads")) {
                if (headAndChild.get("trackchilds") == null
                        || ((Map<String, List<Trackchild>>) headAndChild.get("trackchilds")).get(trackhead.getTrackCode()) == null) {
                    return boundSql.getSql();
                }
                List<Trackchild> listChild = ((Map<String, List<Trackchild>>) headAndChild.get("trackchilds")).get(trackhead.getTrackCode());
                if (listChild != null && listChild.size() > 0) {
                    List<Column> columns = update.getColumns();


                    List<Expression> expressionList = update.getExpressions();
                    StringBuilder sbSql = new StringBuilder("select ");
                    for (int jj = 0; jj < expressionList.size(); jj++) {
                        if (jj == 0) {
                            sbSql.append(expressionList.get(jj));
                        } else {
                            sbSql.append(",").append(expressionList.get(jj));
                        }
                    }
                    sbSql.append(" from dual");
                    String sbTempSql = sbSql.toString();
                    if (parameterObject == null) {
                        throw new NullPointerException(" error，parameterObject is null!");
                    }
                    //这里加判断的原因是暂时只支持BasicModel的子类作为参数，List、Map等暂不支持
                    if (!(parameterObject instanceof BasicModel)) {
                        throw new IllegalArgumentException(" error，argument class must be extends BasicModel!");
                    }
                    BasicModel model = (BasicModel) parameterObject;
                    Method getId = model.getClass().getMethod("getId", model.getClass().getDeclaredClasses());
                    List<Field> fields = InterceptorUtils.getFieldList(model.getClass());
                    for (int ii = 0; ii < parameterMappings.size(); ii++) {
                        Object obj = InterceptorUtils.dynamicGet(model, fields, parameterMappings.get(ii).getProperty());
                        if (obj == null) {
                            sbTempSql = replaceFirst(null, sbTempSql);
                        } else {
                            sbTempSql = replaceFirst(obj.toString(), sbTempSql);
                        }
                    }

                    Statement tempStmt = null;
                    tempStmt = CCJSqlParserUtil.parse(sbTempSql);
                    Select select = (Select) tempStmt;
                    List<SelectItem> selectItems = ((PlainSelect) select.getSelectBody()).getSelectItems();
                    StringBuilder sb = new StringBuilder("select ");
                    for (int kk = 0; kk < selectItems.size(); kk++) {
                        if (kk == 0) {
                            sb.append(((SelectExpressionItem) selectItems.get(kk)).getExpression()).append(" as '" + kk + "' ");
                        } else {
                            sb.append(",").append(((SelectExpressionItem) selectItems.get(kk)).getExpression()).append(" as '" + kk + "' ");
                        }
                    }
                    sb.append(" from dual");
                    Map<String, Object> tempResult = jdbcTemplate.queryForMap(sb.toString());

                    Expression expression = update.getWhere();
                    String sbBefor = getBeforSql(columns, expression, tables, map, trackhead);
                    Map<String, Object> resultMap = getWhere(expression, parameterObject, parameterMappings, index, beforAfter, updateSql);
                    Map<String, Object> tempBeforResult = jdbcTemplate.queryForMap(sbBefor, (Object[]) resultMap.get("obj"));

                    for (int i = 0; i < columns.size(); i++) {
                        for (Trackchild trackchild : listChild) {
                            if (!trackchild.getFieldCode().equalsIgnoreCase(columns.get(i).getColumnName())) {
                                continue;
                            }
                            Object resultObj = tempBeforResult.get(i + "");
                            Object beforObj = tempResult.get(i + "");
                            boolean flag = false;
                            if (resultObj == null && beforObj == null) {
                                continue;
                            } else if (resultObj != null && beforObj != null) {
                                if (resultObj instanceof BigDecimal && resultObj != null && resultObj.toString().startsWith("0E")) {
                                    resultObj = "0.0";
                                }
                                if (resultObj instanceof Timestamp) {
                                    resultObj = resultObj.toString().substring(0, beforObj.toString().length());
                                }
                                if (InterceptorUtils.isDecimal(resultObj.toString())) {
                                    resultObj = InterceptorUtils.subZeroAndDot(resultObj.toString());
                                    beforObj = InterceptorUtils.subZeroAndDot(beforObj.toString());
                                }
                                flag = resultObj.toString().equals(beforObj.toString());
                            } else {
                                if (resultObj != null && InterceptorUtils.isDecimal(resultObj.toString())) {
                                    resultObj = InterceptorUtils.subZeroAndDot(resultObj.toString());
                                }
                                if (beforObj != null && InterceptorUtils.isDecimal(beforObj.toString())) {
                                    beforObj = InterceptorUtils.subZeroAndDot(beforObj.toString());
                                }
                                if (resultObj instanceof BigDecimal && resultObj != null && resultObj.toString().startsWith("0E")) {
                                    resultObj = "0";
                                }
                                flag = false;
                            }
                            if (!flag) {
                                TrackDisplay trackDisplay = new TrackDisplay();
                                trackDisplay.setUpdObjType(trackhead.getTrackName());
                                trackDisplay.setUpdObjCode(trackhead.getTrackCode());
                                trackDisplay.setBatchNum(batchNum);
                                trackDisplay.setUpdObjId((Long) getId.invoke(model));
                                trackDisplay.setFieldName(trackchild.getFieldName());
                                trackDisplay.setFieldValue(trackchild.getFieldCode());
                                if (resultObj != null) {
                                    trackDisplay.setUpdBeforeInfo(resultObj.toString());
                                }
                                if (beforObj != null) {
                                    trackDisplay.setUpdInfo(beforObj.toString());
                                }
                                trackDisplay.setOperate("update");
                                trackDisplay.setCorpid(user.getCorpid());
                                trackDisplay.setPostnId(user.getPostnId());
                                trackDisplay.setOrgId(user.getOrgId());
                                trackDisplay.setAttr77(user.getId());
                                trackDisplay.setAttr1(trackchild.getFieldType());
                                trackDisplay.setAttr2(trackchild.getLovType());
                                trackDisplay.setAttr3(trackchild.getDisplayName());
                                trackDisplay.setAttr4(trackchild.getDisplayValue());
                                logger.info("发送kafka信息");
                                logger.info(JSONObject.toJSON(trackDisplay).toString());
                                KafkaSender.sendInfo(KafkaSender.TRACK_TOPIC, JSONObject.toJSON(trackDisplay).toString());
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            LCLogger.withException(e);
        }
        return boundSql.getSql();
    }


    /**
     * 获取where条件中的参数，已经更新后的字段的内容
     *
     * @param expression        更新sql的where内容
     * @param parameterObject   传入的model
     * @param parameterMappings sql 传入的参数
     * @param index
     * @param beforAfter
     * @param updateSql
     * @return
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public Map<String, Object> getWhere(Expression expression, Object parameterObject,
                                        List<ParameterMapping> parameterMappings, Map<String, String> index,
                                        Map<String, Object> beforAfter, String updateSql)
            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Map<String, Object> resultMap = new HashMap<>(10);
        BasicModel model = null;
        Matcher m = REPLACE_PATTERN.matcher(expression.toString());
        int count = 0;
        while (m.find()) {
            count++;
        }
        Object[] obj = new Object[count];
        if (count > 0) {
            if (parameterObject == null) {
                throw new NullPointerException(" error，parameterObject is null!");
            }
            //这里加判断的原因是暂时只支持BasicModel的子类作为参数，List、Map等暂不支持
            if (!(parameterObject instanceof BasicModel)) {
                throw new IllegalArgumentException(" error，argument class must be extends BasicModel!");
            }
            model = (BasicModel) parameterObject;
            if (parameterMappings != null) {
                for (int i = parameterMappings.size() - 1; i >= 0 && count > 0; i--, count--) {
                    try {
                        Method get = model.getClass().getMethod("get"
                                        + InterceptorUtils.getoUpperCaseChar(parameterMappings.get(i).getProperty()),
                                model.getClass().getDeclaredClasses());
                        obj[count - 1] = get.invoke(model);
                    } catch (NoSuchMethodException e) {
                        LCLogger.withException(e);
                    } catch (IllegalAccessException e) {
                        LCLogger.withException(e);
                    } catch (InvocationTargetException e) {
                        LCLogger.withException(e);
                    }
                }
                for (String integer : index.keySet()) {
                    String rgex = "/\\*(.*?)\\*/";
                    Map<String, Object> result = subString(updateSql, index.get(integer));
                    String updateContent = ((String) result.get("str")).replace("=", "").replace("`", "").trim();
                    // 包含“？” 表示这个是传入的参数，否则 则是当前拼接在sql中的例如“new()”、“000”等
                    if ("?".equals(updateContent)) {
                        Method get = model.getClass().getMethod("get" + InterceptorUtils.getoUpperCaseChar(
                                parameterMappings.get((Integer) result.get("number")).getProperty()), model.getClass().getDeclaredClasses());
                        beforAfter.put(index.get(integer).toLowerCase(), get.invoke(model));
                    } else {
                        if ("now()".equals(updateContent)) {
                            beforAfter.put(index.get(integer).toLowerCase(), DateUtil.dateToStr(new Date(), "yyyy-mm-dd hh:mm:ss"));
                        } else {
                            // 先判断这个更新中有几个？号，然后取parameterMappings中的对应的对象值
                            Integer number = countString(updateContent, "\\?");
                            for (int i = 0; i < number; i++) {
                                Method get = model.getClass().getMethod("get" + InterceptorUtils.getoUpperCaseChar(
                                        parameterMappings.get((Integer) result.get("number") + i).getProperty()),
                                        model.getClass().getDeclaredClasses());
                                updateContent = replaceFirst((String) get.invoke(model), updateContent);
                            }
                            // 获取该字段的函数显示内容（真实内容）
                            StringBuilder sb = new StringBuilder("select ");
                            sb.append(updateContent).append(" as result from dual");
                            //该处效率低，后续考虑优化
                            Map<String, Object> fieldResult = jdbcTemplate.queryForMap(sb.toString());
                            beforAfter.put(index.get(integer).toLowerCase(), fieldResult.get("result"));
                        }
                    }
                }
            }
        }
        resultMap.put("obj", obj);
        resultMap.put("beforAfter", beforAfter);
        return resultMap;
    }


    /**
     * 拼接查询的sql
     *
     * @param columns    更新的字段
     * @param expression 最后的条件sql
     * @param tables     更新的表
     * @param map        更新表的别名和表明
     * @param index      记录更新字段的位置
     * @param trackhead  审计追踪配置头信息
     * @return 查询的sql
     */
    public static Map<String, Object> getSql(List<Column> columns, Expression expression, List<Table> tables, Map<String, String> map,
                                             Map<String, String> index, Trackhead trackhead) {
        Map<String, Object> result = new HashMap<>(10);
        StringBuilder sql = new StringBuilder("select ");
        if (columns != null && columns.size() > 0) {
            String tableAlias = map.get(trackhead.getTrackCode());
            StringBuilder tempSql = new StringBuilder();
            for (int i = 0; i < columns.size(); i++) {
                boolean tempFlag = StringUtils.isNotBlank(tableAlias) && tableAlias.equals(columns.get(0).getTable().getName());
                boolean tempNull = StringUtils.isBlank(tableAlias) && StringUtils.isBlank(columns.get(0).getTable().getName());
                if (tempFlag || tempNull) {
                    tempSql.append("," + columns.get(i).toString());
                    index.put(i + "", columns.get(i).getColumnName().replace("`", ""));
                }
            }
            sql.append(tempSql.substring(1));
            if (StringUtils.isNotBlank(tableAlias)) {
                sql.append("," + tableAlias + ".row_id");
            } else {
                sql.append(",row_id");
            }
        }
        sql.append(" from ");
        if (tables != null && tables.size() > 0) {
            sql.append(tables.get(0).toString());
            for (int i = 1; i < tables.size(); i++) {
                sql.append("," + tables.get(i).toString());
            }
        }
        sql.append(" where " + expression.toString());
        result.put("index", index);
        result.put("sql", sql);
        return result;
    }

    /**
     * 拼接查询的sql
     *
     * @param columns    更新的字段
     * @param expression 最后的条件sql
     * @param tables     更新的表
     * @param map        更新表的别名和表明
     * @param trackhead  审计追踪配置头信息
     * @return 查询的sql
     */
    public static String getBeforSql(List<Column> columns, Expression expression, List<Table> tables, Map<String, String> map,
                                     Trackhead trackhead) {
        StringBuilder sql = new StringBuilder("select ");
        if (columns != null && columns.size() > 0) {
            String tableAlias = map.get(trackhead.getTrackCode());
            StringBuilder tempSql = new StringBuilder();
            for (int i = 0; i < columns.size(); i++) {
                boolean tempFlag = StringUtils.isNotBlank(tableAlias) && tableAlias.equals(columns.get(0).getTable().getName());
                boolean tempNull = StringUtils.isBlank(tableAlias) && StringUtils.isBlank(columns.get(0).getTable().getName());
                if (tempFlag || tempNull) {
                    tempSql.append("," + columns.get(i).toString()).append(" as '" + i + "' ");
                }
            }
            sql.append(tempSql.substring(1));
        }
        sql.append(" from ");
        if (tables != null && tables.size() > 0) {
            sql.append(tables.get(0).toString());
            for (int i = 1; i < tables.size(); i++) {
                sql.append("," + tables.get(i).toString());
            }
        }
        sql.append(" where " + expression.toString());
        return sql.toString();
    }


    /**
     * 截取字符串str中指定字符 strStart、strEnd之间的字符串
     *
     * @param str
     * @param strStart
     * @return
     */
    public static Map<String, Object> subString(String str, String strStart) {
        Map<String, Object> map = new HashMap<>(10);
        /* 找出指定的字符在 该字符串里面的 位置 */
        str = str.toLowerCase();
        int strStartIndex = getFieldIndex(str, strStart);
        //判断当前这个字段前有多少个？号
        int number = countString(str.substring(0, strStartIndex), "\\?");
        map.put("number", number);
        str = str.substring(strStartIndex);
        strStartIndex = 0;
        // 判断当为最后一个字段的时候，应该使用“where”，当不为最后一个字段的时候为“,”
        int strEndIndex = str.indexOf(",");
        /* 开始截取 */
        if (strEndIndex < 0) {
            strEndIndex = str.indexOf(WHERE);
            if (strEndIndex < 0) {
                map.put("str", str);
                return map;
            }
        }
        String result = str.substring(strStartIndex, strEndIndex).substring(strStart.length());
        if (StringUtils.isBlank(result) || result.contains(WHERE)) {
            int strWhereEndIndex = str.indexOf(WHERE);
            result = str.substring(strStartIndex, strWhereEndIndex).substring(strStart.length());
        } else {
            boolean lastField = str.substring(str.indexOf(result) + result.length()).startsWith(WHERE);
            if (!lastField) {
                result = result + getAllField(str, strEndIndex);
            }
        }
        map.put("str", result);
        return map;
    }


    /**
     * 获取字段在sql中的精确位置，防止出现 字段名包含字段名情况
     *
     * @param str   sql
     * @param field 字段名
     * @return 字段所在位置
     */
    public static int getFieldIndex(String str, String field) {
        int index = str.indexOf(field.toLowerCase());
        str = str.substring(index + field.length());
        if (!str.trim().startsWith(EQUALS)) {
            index = index + field.length() + getFieldIndex(str, field);
            return index;
        } else {
            return index;
        }
    }


    /**
     * 此方法获取当更新的字段值是一个方法的时候并且方法中包含多个传入的参数获取整个方法
     *
     * @param str    传入的sql
     * @param number 截取位置
     * @return
     */
    public static String getAllField(String str, Integer number) {
        String tempStr = str.substring(number + 1);
        //这里如果当最后一个字段为WARM_DT = str_to_date(lnk_blank_str_to_null(?), '%Y-%m-%d %H:%i:%S') where T.ROW_ID = ? 这种形式的时候，那么截取后 就不存在，这里需要考虑
        Integer tempIndex = tempStr.indexOf(",");
        String temp = "";
        if (tempIndex > 0) {
            temp = tempStr.substring(0, tempIndex);
            if (!(temp.contains(EQUALS) || temp.contains(WHERE))) {
                return "," + temp + getAllField(tempStr, tempIndex);
            } else {
                return "";
            }
        } else {
            Integer whereInteger = tempStr.indexOf(WHERE);
            if (whereInteger < 0) {
                return "," + tempStr;
            } else {
                if (tempStr.substring(0, whereInteger).contains("=")) {
                    return "";
                }
                return "," + tempStr.substring(0, whereInteger);
            }
        }
    }


    /**
     * 根据表别名获取表名
     *
     * @param map
     * @param value
     * @return
     */
    public String getKey(Map<String, String> map, String value) {
        for (String key : map.keySet()) {
            if (map.get(key).equals(value)) {
                return key;
            }
        }
        return null;
    }

    /**
     * 统计一个字符串中某个字符或字符串出现的次数
     *
     * @param str 字符串
     * @param s   包含的字符串
     * @return 出现的次数
     */
    public static int countString(String str, String s) {
        int count = 0;
        String[] arr = str.split(s);
        int len = arr.length;
        if (str.endsWith(s)) {
            //如果子串在末尾
            len++;
        }
        count = len - 1;
        return count;
    }


    /**
     * 替换sql中的的？
     *
     * @param str
     * @param updateSql 传入的sql
     * @return 替换后的sql
     */
    public static String replaceFirst(String str, String updateSql) {
        if (StringUtils.isBlank(str)) {
            str = "null";
        } else {
            str = "'" + str + "'";
        }
        Matcher m = REPLACE_PATTERN.matcher(updateSql);
        String temp = m.replaceFirst(str);
        return temp;
    }
}
